#pragma once

#include <assert.h>
#include <string.h>  // for memset

#define TIMER_WHEEL_COUNT 4
#define TIMER_WHEEL_BITS 6
#define TIMER_BUCKET_COUNT (1 << TIMER_WHEEL_BITS)
#define TIMER_WHEEL_CUR_INDEX(_n) \
    ((timer_mgr->timeout >> TIMER_WHEEL_BITS * (_n)) % TIMER_BUCKET_COUNT)
#define TIMER_MAX_TIMEOUT \
    (((unsigned long long)1 << (TIMER_WHEEL_COUNT * TIMER_WHEEL_BITS)) - 1)

typedef struct wtimer_s wtimer_t;
typedef struct wtimer_mgr_s wtimer_mgr_t;
typedef void (*timer_cb)(wtimer_t* timer);
typedef void (*timer_debug_dump_cb)(
    int wheel, int bucket, wtimer_t* timer);  // only for debug
struct wtimer_s
{
    timer_cb cb;
    unsigned long long timeout;
    unsigned long long repeat;
    struct wtimer_s *prev, *next;
};

typedef struct
{
    wtimer_t buckets[TIMER_BUCKET_COUNT];
} wtimer_wheel_t;

struct wtimer_mgr_s
{
    unsigned long long timeout;
    wtimer_wheel_t wheels[TIMER_WHEEL_COUNT];
};

static inline void timer_mgr_init(
    wtimer_mgr_t* timer_mgr, unsigned long long now_time) {
    memset(timer_mgr, 0, sizeof(wtimer_mgr_t));
    timer_mgr->timeout = now_time;
    for (int i = 0; i < TIMER_WHEEL_COUNT; ++i) {
        wtimer_wheel_t* wheel = &timer_mgr->wheels[i];
        for (int j = 0; j < TIMER_BUCKET_COUNT; ++j) {
            wtimer_t* bucket = &wheel->buckets[j];
            bucket->prev = bucket->next = bucket;
        }
    }
}

static void timer_mgr_debug_dump(
    wtimer_mgr_t* timer_mgr, timer_debug_dump_cb cb) {
    for (int i = 0; i < TIMER_WHEEL_COUNT; ++i) {
        wtimer_wheel_t* wheel = &timer_mgr->wheels[i];
        for (int j = 0; j < TIMER_BUCKET_COUNT; ++j) {
            wtimer_t* bucket = &wheel->buckets[j];
            wtimer_t* timer = bucket->next;
            while (timer != bucket) {
                wtimer_t* next = timer->next;
                cb(i, j, timer);
                timer = next;
            }
        }
    }
}

// timeout: first trigger time
// repeat:  loop interval time after first trigger
static inline int timer_start(wtimer_mgr_t* timer_mgr, wtimer_t* timer,
    timer_cb cb, unsigned long long timeout, unsigned long long repeat) {
    if (NULL == cb || TIMER_MAX_TIMEOUT < timeout) {
        return -1;
    }
    unsigned long long clamped_timeout = timer_mgr->timeout + timeout;
    if (clamped_timeout < timeout) {
        clamped_timeout = (unsigned long long)~0;
    }
    timer->cb = cb;
    timer->timeout = clamped_timeout;
    timer->repeat = repeat;

    int widx = 0, cur = TIMER_WHEEL_CUR_INDEX(0),
        bidx = timeout % TIMER_BUCKET_COUNT;
    while (timeout >= TIMER_BUCKET_COUNT) {
        widx += 1;
        timeout >>= TIMER_WHEEL_BITS;
        bidx = timeout % TIMER_BUCKET_COUNT;
        cur = TIMER_WHEEL_CUR_INDEX(widx);
    }
    if (TIMER_WHEEL_COUNT <= widx) {
        return -2;
    }
    wtimer_wheel_t* wheel = &timer_mgr->wheels[widx];
    wtimer_t* bucket = &wheel->buckets[(cur + bidx) % TIMER_BUCKET_COUNT];
    timer->prev = bucket->prev;
    timer->next = bucket;
    timer->prev->next = timer;
    bucket->prev = timer;
    return 0;
}

static inline int timer_stop(wtimer_t* timer) {
    if (timer->prev && timer->next) {
        timer->prev->next = timer->next;
        timer->next->prev = timer->prev;
        timer->prev = timer->next = NULL;
    }
    return 0;
}

static inline int timer_again(wtimer_mgr_t* timer_mgr, wtimer_t* timer) {
    if (NULL == timer->cb) {
        return -1;
    }
    if (timer->repeat) {
        timer_stop(timer);
        timer_start(timer_mgr, timer, timer->cb, timer->repeat, timer->repeat);
    }
    return 0;
}

static inline int timer_cascade(wtimer_mgr_t* timer_mgr, int widx, int bidx) {
    wtimer_wheel_t* wheel = &timer_mgr->wheels[widx];
    wtimer_t* bucket = &wheel->buckets[bidx];
    wtimer_t* timer = bucket->next;
    while (timer != bucket) {
        wtimer_t* next = timer->next;
        assert(timer->timeout >= timer_mgr->timeout);
        unsigned long long timeout = timer->timeout - timer_mgr->timeout;
        // reinsert
        timer_stop(timer);
        timer_start(timer_mgr, timer, timer->cb, timeout, timer->repeat);
        timer = next;
    }
    return bidx;
}

static inline int timer_tick(
    wtimer_mgr_t* timer_mgr, unsigned long long now_time) {
    while (timer_mgr->timeout <= now_time) {
        int bidx = timer_mgr->timeout % TIMER_BUCKET_COUNT;
        if (!bidx) {
            int widx = 1;
            while (
                widx < TIMER_WHEEL_COUNT &&
                !timer_cascade(timer_mgr, widx, TIMER_WHEEL_CUR_INDEX(widx))) {
                ++widx;
            }
        }
        wtimer_t* bucket = &timer_mgr->wheels[0].buckets[bidx];
        wtimer_t* timer = bucket->next;
        while (timer != bucket) {
            wtimer_t* next = timer->next;
            timer_stop(timer);
            timer_again(timer_mgr, timer);
            timer->cb(timer);
            timer = next;
        }
        ++timer_mgr->timeout;
    }
    return 0;
}